/*************************************************
* MD4 Source File                                *
* (C) 1999-2002 The Botan Project                *
* Modifications (C) 2003 Llew goodstadt          *
*************************************************/

#include "md4.h"

namespace mini_Botan {

inline void MD4::copy(byte buf[], unsigned off, const byte in[], unsigned n)
   {   std::memcpy(buf + off, in, std::min(64 - off, n));}
inline unsigned MD4::make_unsigned(byte input0, byte input1, byte input2, byte input3)
{
	return(unsigned)(((unsigned)input0 << 24) | ((unsigned)input1 << 16) |
				   ((unsigned)input2 <<  8) | input3);
}
/*************************************************
* Byte Extraction Function                       *
*************************************************/
template<typename T> MD4::byte MD4::get_byte(unsigned byte_num, T input)
{
	return(MD4::byte)(input >> ((sizeof(T)-1-(byte_num&(sizeof(T)-1))) << 3));
}
/*************************************************
* MDx_HashFunction Constructor                   *
*************************************************/
MD4::MD4()
{
	clear();
}


void MD4::process(const std::string& in, unsigned result[])
{
	const byte* input = (const byte*) in.c_str();
	unsigned length = in.length();
	// add data
	count += length;
	copy(buffer, position, input, length);
//	std::memcpy(buffer + position, input, std::min(64 - position, length));
	if (position + length >= HASH_BLOCK_SIZE)
	{
		hash(buffer);
		input += (HASH_BLOCK_SIZE - position);
		length -= (HASH_BLOCK_SIZE - position);
		while (length >= HASH_BLOCK_SIZE)
		{
			hash(input);
			input += HASH_BLOCK_SIZE;
			length -= HASH_BLOCK_SIZE;
		}
		copy(buffer, 0, input, length);
		position = 0;
	}
	position += length;
	
	// Hashing/MACing
	byte* b_result = (byte*) result;
	buffer[position] = 0x80;
	for (unsigned j = position+1; j != HASH_BLOCK_SIZE; j++)
		buffer[j] = 0;
	if (position >= HASH_BLOCK_SIZE - 8)
	{
		hash(buffer);
		std::memset(buffer, 0, sizeof(buffer));
	}
	// remember to keep count a 64 bit value for digest extraction
	for (unsigned j = HASH_BLOCK_SIZE - 8; j != HASH_BLOCK_SIZE; j++)
		buffer[j] = get_byte(7 - (j % 8), 8 * count);
	hash(buffer);
	for (unsigned j = 0; j != OUTPUT_LENGTH; j++)
		b_result[j] = get_byte(3 - (j % 4), digest[j/4]);
	clear();
}


/*************************************************
* MD4 Hash                                       *
*************************************************/
void MD4::hash(const byte input[])
{
	for (unsigned j = 0; j != 16; j++)
		M[j] = make_unsigned(input[4*j+3], input[4*j+2], input[4*j+1], input[4*j]);

	unsigned A = digest[0], B = digest[1], C = digest[2], D = digest[3];

	FF(A,B,C,D,M[ 0], 3);   FF(D,A,B,C,M[ 1], 7);   FF(C,D,A,B,M[ 2],11);
	FF(B,C,D,A,M[ 3],19);   FF(A,B,C,D,M[ 4], 3);   FF(D,A,B,C,M[ 5], 7);
	FF(C,D,A,B,M[ 6],11);   FF(B,C,D,A,M[ 7],19);   FF(A,B,C,D,M[ 8], 3);
	FF(D,A,B,C,M[ 9], 7);   FF(C,D,A,B,M[10],11);   FF(B,C,D,A,M[11],19);
	FF(A,B,C,D,M[12], 3);   FF(D,A,B,C,M[13], 7);   FF(C,D,A,B,M[14],11);
	FF(B,C,D,A,M[15],19);

	GG(A,B,C,D,M[ 0], 3);   GG(D,A,B,C,M[ 4], 5);   GG(C,D,A,B,M[ 8], 9);
	GG(B,C,D,A,M[12],13);   GG(A,B,C,D,M[ 1], 3);   GG(D,A,B,C,M[ 5], 5);
	GG(C,D,A,B,M[ 9], 9);   GG(B,C,D,A,M[13],13);   GG(A,B,C,D,M[ 2], 3);
	GG(D,A,B,C,M[ 6], 5);   GG(C,D,A,B,M[10], 9);   GG(B,C,D,A,M[14],13);
	GG(A,B,C,D,M[ 3], 3);   GG(D,A,B,C,M[ 7], 5);   GG(C,D,A,B,M[11], 9);
	GG(B,C,D,A,M[15],13);

	HH(A,B,C,D,M[ 0], 3);   HH(D,A,B,C,M[ 8], 9);   HH(C,D,A,B,M[ 4],11);
	HH(B,C,D,A,M[12],15);   HH(A,B,C,D,M[ 2], 3);   HH(D,A,B,C,M[10], 9);
	HH(C,D,A,B,M[ 6],11);   HH(B,C,D,A,M[14],15);   HH(A,B,C,D,M[ 1], 3);
	HH(D,A,B,C,M[ 9], 9);   HH(C,D,A,B,M[ 5],11);   HH(B,C,D,A,M[13],15);
	HH(A,B,C,D,M[ 3], 3);   HH(D,A,B,C,M[11], 9);   HH(C,D,A,B,M[ 7],11);
	HH(B,C,D,A,M[15],15);

	digest[0] += A;   digest[1] += B;   digest[2] += C;   digest[3] += D;
}

/*************************************************
* Rotation Functions                             *
*************************************************/
template<typename T> inline T rotate_left(T input, unsigned rot)
{
	return(T)((input << rot) | (input >> (8*sizeof(T)-rot)));
}

/*************************************************
* MD4 FF Function                                *
*************************************************/
void MD4::FF(unsigned& A, unsigned B, unsigned C, unsigned D, unsigned M, byte S)
{
	A += (D ^ (B & (C ^ D))) + M;
	A  = rotate_left(A, S);
}

/*************************************************
* MD4 GG Function                                *
*************************************************/
void MD4::GG(unsigned& A, unsigned B, unsigned C, unsigned D, unsigned M, byte S)
{
	A += ((B & C) | (D & (B | C))) + M + 0x5A827999;
	A  = rotate_left(A, S);
}

/*************************************************
* MD4 HH Function                                *
*************************************************/
void MD4::HH(unsigned& A, unsigned B, unsigned C, unsigned D, unsigned M, byte S)
{
	A += (B ^ C ^ D) + M + 0x6ED9EBA1;
	A  = rotate_left(A, S);
}



/*************************************************
* Clear memory of sensitive data                 *
*************************************************/
void MD4::clear()
{
	std::memset(M, 0, sizeof(M));
	std::memset(buffer, 0, sizeof(buffer));
	digest[0] = 0x67452301;
	digest[1] = 0xEFCDAB89;
	digest[2] = 0x98BADCFE;
	digest[3] = 0x10325476;
	count		= 0;
	position	= 0;
}

}
